Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change project details to render the layout before loading the resour… #6454

Conversation

Richard-Pentecost
Copy link
Contributor

…ce, create new layout and layout header for investments

Description of change

Change the project details page so that the layout is rendered before the resource is called. So that if there is an error then the error messages show in the layout. Create a new project layout, investment local header and investment layout component. Add props to the Investment project local header after it has been called. Inject inline resource into the new project layout component.

Test instructions

In the project details component put in a fake project id

Screenshots

Before

image

After

image

Checklist

  • Has the branch been rebased to main?
  • Automated tests (Any of the following when applicable: Unit, Functional or End-to-End)
  • Manual compatibility testing (Browsers: Chrome, Firefox, Edge, Safari)

…ce, create new layout and layout header for investments
@Richard-Pentecost Richard-Pentecost requested a review from a team as a code owner January 23, 2024 12:01
@Richard-Pentecost Richard-Pentecost changed the base branch from main to feature-branch/TET-613-RefactorInvestmentProject January 23, 2024 12:02
Copy link

cypress bot commented Jan 23, 2024

Passing run #50436 ↗︎

0 26 0 0 Flakiness 0

Details:

Fix ProjectLayoutNew so end-to-end tests pass
Project: data-hub-frontend Commit: 7636733d00
Status: Passed Duration: 02:06 💡
Started: Jan 24, 2024 12:30 PM Ended: Jan 24, 2024 12:32 PM

Review all test suite changes for PR #6454 ↗︎

@Richard-Pentecost Richard-Pentecost merged commit 5c8532d into feature-branch/TET-613-RefactorInvestmentProject Jan 24, 2024
14 checks passed
@Richard-Pentecost Richard-Pentecost deleted the feature/project-details-refactor branch January 24, 2024 13:44
Copy link
Contributor

@peterhudec peterhudec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the PR has been merged before I could finish my review. Anyway, I'm submitting it for posterity.

Comment on lines +50 to +56
const renderChildren = (project) => {
return Children.map(children, (child) => {
return cloneElement(child, {
investment: project,
})
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid this mechanism as it makes components that are dependent on each other appear to be independent. In this particular case, <InvestmentProjectLocalHeader /> doesn't seem to be dependent to <InvestmentLayout/> in any way, but in fact it depends on <InvestmentLayout/> to inject the investment property to <InvestmentProjectLocalHeader />.

<InvestmentLayout
  // ...
  localHeaderChildren={<InvestmentProjectLocalHeader />}
>
  {/* ... */}
</InvestmentLayout>

One day anothe developer might want to tweak the markup (for whatever reasons) and wrap the <InvestmentProjectLocalHeader /> in a <div/> and they would have a hard time figuring out why do they get a "TypeError: Cannot read properties of undefined (reading 'status')"?

<InvestmentLayout
  // ...
  // This will throw an error because InvestmentProjectLocalHeader won't be called with the required
  // investment prop. The <div/> will receive it instead.
  localHeaderChildren={<div><InvestmentProjectLocalHeader /></div>}
>
  {/* ... */}
</InvestmentLayout>

I would prefer that localHeaderChildren prop was a function which would recieve the investment as an argument, which would make the dependency obvious:

<InvestmentLayout
  // ...
  localHeaderChildren={investment => <InvestmentProjectLocalHeader  investment={investment}/>}
>
  {/* ... */}
</InvestmentLayout>

Or even better, if InvestmentLayout is only ever going to be used once, I would just inline the body of InvestmentProjectLocalHeader into InvestmentLayout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much for this description. I was really struggling with trying to add props to the component when the component was being created before the props were available, hence the method I used. Using a function is a lot better, however I think the inline method will work for this situation as it will only be used on investment pages.

<LocalHeaderHeading data-test="heading">
{project.name}
</LocalHeaderHeading>
{renderChildren(project)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just inline the body of InvestmentProjectLocalHeader here.

Copy link
Contributor Author

@Richard-Pentecost Richard-Pentecost Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I've put in inline

Comment on lines +37 to +54
export const ProjectLayoutNew = ({
projectId,
projectName,
pageTitle,
children,
userPermissions,
flashMessages,
}) => {
return (
<InvestmentLayout
projectId={projectId}
heading={projectName}
pageTitle={`${pageTitle} - ${projectName} - Projects - Investments`}
breadcrumbs={buildProjectBreadcrumbs({ text: projectName })}
flashMessages={flashMessages}
useReactRouter={false}
localHeaderChildren={<InvestmentProjectLocalHeader />}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that both InvestmentLayout and ProjectLayoutNew are only ever going to be used once in ProjectDetails and the only thing ProjectLayoutNew is doing is to hard code or transform some of the prop values. Wouldn't it be simpler to get rid of these components and have it all hardcoded in ProjectDetails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ProjectLayoutNew will be used in all investment pages.

margin-top: 0;
`

const StyledLink = styled('a')({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we use the govuk-react Link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've changed this. This was a legacy thing from copying over the local header component and changing it for our use

</Breadcrumbs.Link>
)
) : (
<Fragment key={breadcrumb.text}>{breadcrumb.text}</Fragment>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fragment is redundant if you are only rendering a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to prevent a unique key prop error we need to add a key. So the fragment is in to allow us to add a key to the element without actually using an html element.

Comment on lines +29 to +31
useEffect(() => {
document.title = `${pageTitle} - DBT Data Hub`
}, [pageTitle])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can now use the WatchTextContent component to also accept React vdom as pageTitle value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating this we didn't have access to the WatchTextContent. I have rebased the main, and have now implemented the WatchTextContent component

Comment on lines +36 to +37
showVerticalNav={showVerticalNav}
onShowVerticalNav={setShowVerticalNav}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem of this PR, but DataHubHeader should handle its state internally and not require this plumbing everywhere it is used. We can do better than that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's strange that the Datahub header itself doesn't manage this state

Comment on lines +42 to +44
breadcrumbs={
breadcrumbs || [{ link: '/', text: 'Home' }, { text: heading }]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? It seems that breadcrumbs will always be defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the or check

Comment on lines +40 to +45
projectId={projectId}
flashMessages={flashMessages}
breadcrumbs={
breadcrumbs || [{ link: '/', text: 'Home' }, { text: heading }]
}
useReactRouter={useReactRouter}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are just forwarding props with the same name here so you can just keep them in ...props and spread them here:

<InvestmentLocalHeader {...props}>
  {localHeaderChildren}
</InvestmentLocalHeader>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored to use ...props

Comment on lines +19 to +21
const StyledNavWrapper = styled('div')`
margin-bottom: ${SPACING.SCALE_5};
`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really necessary? I guess it's here to prevent the navigation touching the page footer, but the fundamental issue is that the footer is not sticking to the bottom of the page if the content is too small.

This should be fixed on the layout level where the layout would be wrapped in a <div/> with display: flex; flex-direction: column and its <main/> child should have flex-grow: 1 so that it pushes the footer to the bottom. The problem is that for this to work the <div/>, the <div id="react-app"/>, <body/> and <html/> need to have height: 100%.

<html style="height: 100%">
  <body style="height: 100%">
    <div id="react-app" style="height: 100%" >
      <div style="display: flex; flex-direction: column"><!--layout wrapper-->
        <header></header>
        <div aria-label="local header"></div>
        <main style="flex-grow: 1"></main><!--fills remaining space-->
        <footer></footer>
      </div>
    </div>
  </body>
</html>
Screenshot 2024-01-24 at 14 51 18

Copy link
Contributor Author

@Richard-Pentecost Richard-Pentecost Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a look at this, but it's bit out of scope for this ticket. Couldn't get <div id=react-app> to take the full height even after adding height: 100%. This file is a copy of the project layout that has been tweaked and so the StyledNavWrapper is a pre existing component from that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants